package trading_strategy.strategies;

import org.apache.log4j.Logger;
import trading_strategy.Strategy;
import trading_strategy.events.*;
import trading_strategy.instruments.ListedInstrument;
import trading_strategy.orders.IOCLimitOrder;
import trading_strategy.orders.LimitWithStopLossOrder;
import trading_strategy.orders.Order;
import trading_strategy.orders.SingleOrderAutomaton;
import trading_strategy.positions.InstrumentPosition;
import trading_strategy.positions.Trade;

import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Contrarian strategy
 * <p/>
 * Created by IntelliJ IDEA.
 * User: gderoujoux
 * Date: 6 mai 2010
 * Time: 14:42:06
 * To change this template use File | Settings | File Templates.
 */
public class SuccessiveTicksStrategy extends Strategy implements SingleOrderAutomaton.OrderProvider {
    LinkedList<PublicTradeUpdate> lastTrades = new LinkedList<PublicTradeUpdate>();
    final ListedInstrument instrument;
    final SingleOrderAutomaton automaton;
    int nbOfTicksUp = 5;
    int nbOfTicksDown = 5;

    int targetProfitInTicks = 3;
    int stopLossInTicks = 2;

    Order lastOrderToSend;
    long nextTsPossibleToSend = 0;
    int accumulatedQty = 0;
    private LimitWithStopLossOrder liquidatingOrder;
    private static final Logger LOGGER = Logger.getLogger(SuccessiveTicksStrategy.class);
    private InstrumentPosition position;

    public SuccessiveTicksStrategy(ListedInstrument instrument) {
        this.instrument = instrument;
        this.automaton = new SingleOrderAutomaton(instrument, this);
        this.position = new InstrumentPosition(instrument);
    }

    @Override
    public void onMarketEvent(AbstractMarketEvent event) {
        if (!isStarted()) {
            if (event instanceof ScheduledEvent) {
                ScheduledEvent se = (ScheduledEvent) event;
                if (se.getType() == ScheduledEvent.EventType.START) {
                    start();
                }
            } else if (event instanceof MarketEvent) {
                MarketEvent me = (MarketEvent) event;
                switch (me.getState()) {
                    case OPEN:
                        getEventDispatcher().addScheduledEvent(new ScheduledEvent(ScheduledEvent.EventType.START, event.getTs(), 5 * 60 * 1000));
                        break;
                    default:
                        LOGGER.warn("Don't know what to do with this market state " + me.getState());
                }

                return;
            }
            return;
        }
        synchronized (this) {
            if (accumulatedQty != 0 || liquidatingOrder != null) {
                // We need to liquidate accumulated position before trading again...
                return;
            }
            if (event instanceof PublicTradeUpdate) {
                PublicTradeUpdate newTrade = (PublicTradeUpdate) event;
                if (lastTrades.isEmpty()) {
                    lastTrades.addFirst(new PublicTradeUpdate(newTrade));
                } else {
                    PublicTradeUpdate previousTrade = lastTrades.getFirst();
                    if (previousTrade.getPrice() != newTrade.getPrice()) {
                        lastTrades.addFirst(new PublicTradeUpdate(newTrade));
                    } else {
                        previousTrade.setQty(previousTrade.getQty() + newTrade.getQty());
                        return;
                    }

                    double diff = previousTrade.getPrice() - newTrade.getPrice();
                    boolean tickUp = diff < 0;
                    int nbOfTicks = tickUp ? nbOfTicksDown : nbOfTicksUp;
                    if (lastTrades.size() >= (nbOfTicks + 1)) {
                        Iterator<PublicTradeUpdate> tit = lastTrades.iterator();
                        tit.next(); // Ignore the newly added trade
                        tit.next(); // Ignore the previous trade

                        double lastPrice = previousTrade.getPrice();
                        int nbOfSuccessiveTicks; // Already 1 tick in the direction
                        boolean broken = false;
                        for (nbOfSuccessiveTicks = 1; nbOfSuccessiveTicks < nbOfTicks; nbOfSuccessiveTicks++) {
                            PublicTradeUpdate t = tit.next();
                            double tmpDiff = t.getPrice() - lastPrice;
                            lastPrice = t.getPrice();
                            if (tmpDiff * diff <= 0) {
                                tit.remove(); // Remove the current trade as it breaks the successive tick sign rule
                                broken = true;
                                break;
                            }
                        }

                        if (!broken) {
                            int i = 1;
                        }
                        if (nbOfSuccessiveTicks >= nbOfTicks) {
                            LOGGER.debug("Hit tick " + (tickUp ? "Up" : "Down"));
                            int qty;
                            double price;
                            if (tickUp) {
                                // Sell at selected price ??
                                qty = -1;
                                price = newTrade.getPrice();
                            } else {
                                // Buy at selected price ??
                                qty = 1;
                                price = newTrade.getPrice();
                            }
                            if (event.getTs() >= nextTsPossibleToSend) {
                                lastOrderToSend = new IOCLimitOrder(instrument, qty, price, getClass().getName(), automaton, event.getTs());
                                nextTsPossibleToSend = event.getTs() + 10 * 1000000; // 10 seconds
                                automaton.sendOrder(lastOrderToSend);
                            }
                        }

                        if (!broken && tit.hasNext()) {
                            tit.next(); // Keep 1 additional trade
                        }
                        // Remove all further ticks as they are not needed anymore
                        while (tit.hasNext()) {
                            tit.next();
                            tit.remove();
                        }
                    }
                }
            }
        }
    }

    @Override
    public boolean shouldStillSendOrder(Order order) {
        synchronized (this) {
            return true;
        }
    }

    @Override
    public void onExecution(int qty, double price, Order order) {
        synchronized (this) {
            position.addTrade(new Trade(order.getMatchingTimestamp(), new Date(order.getMatchingTimestamp() / 1000), qty, price, getClass().getName()));
            LOGGER.info("Execution " + qty + "@" + price + " at " + new java.util.Date(order.getMatchingTimestamp() / 1000));
            accumulatedQty += qty;
            if (accumulatedQty == 0) {
                automaton.cancel();
                liquidatingOrder = null;
            } else {
                if (liquidatingOrder != null) {
                    // TODO check whether the order is still in the market with appropriate quantity
                } else {
                    final int side = qty > 0 ? 1 : -1; // 1 if we have just bought and need to sell, -1 otherwise
                    double targetPrice = price + side * targetProfitInTicks * instrument.getTick();
                    double stopLoss = price - side * stopLossInTicks * instrument.getTick();
                    liquidatingOrder = new LimitWithStopLossOrder(instrument, -qty, targetPrice, stopLoss, "Liquidating position", automaton, order.getMatchingTimestamp());
                    automaton.sendOrder(liquidatingOrder);
                }
            }
        }
    }

    @Override
    public void start() {
        lastTrades.clear();
        super.start();
    }

    public void register() {
        // Subscribe to events classes
        EventManager.getInstance().subscribe(getListener(), instrument.getTradeEventProvider());
        EventManager.getInstance().subscribe(getListener(), instrument.getMarket().getMarketEventProvider());
    }

    @Override
    public double getPnL() {
        return position.getPnL();
    }
}
